// OGoMojo
// Whitemice.MOGI.Config
// License: LGPL
// Copyright: Whitemice Consulting, 2006
// Author(s): Adam Tauno Williams (awilliam@whitemice.org)
using System;
using System.IO;
using System.Collections;
using System.DirectoryServices;
using Whitemice.MOGI;
using Nini.Config;

namespace Whitemice.MOGI {
	public enum AttributeType : uint 
	{
		Undefined = 0,
		String = 1,
		Boolean = 2,
		Mail = 3,
		Select = 127,
		MultiSelect = 255
	}

	/// \brief A wrapper class around the servers INI file
	/// \todo We should support loading the configuration from the network
	public class Configuration
	{
		IConfigSource	ini;
		Hashtable		configs;
		
		/// \brief Constructor
		/// \note Current this tries to open OGoMojo.ini in the current working directory,
		/// then the home directory of the current user, and finally from /etc
		public Configuration()
		{
			configs = new Hashtable();
			if (File.Exists("OGoMojo.ini"))
				ini = new IniConfigSource("OGoMojo.ini");
				else
					if (File.Exists("~/OGoMojo.ini"))
						ini = new IniConfigSource("~/OGoMojo.ini");
						else ini = new IniConfigSource("/etc/OGoMojo.ini");
			configs.Add("Contact", ParseContactStanza());
			configs.Add("Enterprise", ParseEnterpriseStanza());
		}
		
		/// \brief Read the Contact stanza into the hashtable
		protected Hashtable ParseContactStanza() 
		{
			Hashtable	contact;
			
			contact = new Hashtable();
			contact.Add("Phones", ParsePhoneTypes("Contact"));
			contact.Add("Addresses", ParseAddressTypes("Contact"));
			contact.Add("Attributes", ParseAttributes("Contact"));
			return contact;
		}
		
		/// \brief Read the Enterprise stanza into the hashtable
		protected Hashtable ParseEnterpriseStanza()
		{
			Hashtable enterprise;
			
			enterprise = new Hashtable();
			enterprise.Add("Phones", ParsePhoneTypes("Enterprise"));
			enterprise.Add("Addresses", ParseAddressTypes("Enterprise"));
			enterprise.Add("Attributes", ParseAttributes("Enterprise"));
			return enterprise;
		}
		
		/// \brief Parse the phone types from the specified stanza
		/// \param stanza Stanza to parse
		protected Hashtable ParsePhoneTypes(string stanza)
		{
			string[]	phoneTypes;
			Hashtable	phones;
			Hashtable	phone;
		
			phones = new Hashtable();
			phoneTypes = ini.Configs[stanza].Get("Phones").Split(',');
			foreach (string phoneType in phoneTypes)
			{
				phone = new Hashtable();
				phone.Add("Name", ini.Configs[stanza].Get(phoneType));
				phones.Add(phoneType, phone);
			}
			return phones;
		}

		/// \brief Parse the address types from the specified stanza
		/// \param stanza Stanza to parse		
		protected Hashtable ParseAddressTypes(string stanza)
		{
			string[]	addressTypes;
			Hashtable	addresses;
			Hashtable	address;
		
			addresses = new Hashtable();
			addressTypes = ini.Configs[stanza].Get("Addresses").Split(',');
			foreach (string addressType in addressTypes)
			{
				address = new Hashtable();
				address.Add("Name", ini.Configs[stanza].Get(addressType));
				addresses.Add(addressType, address);
			}
			return addresses;
		}		
		
		/// \brief Parse the extended attribute definitions from the specified stanza
		/// \param stanza Stanza to parse		
		protected Hashtable ParseAttributes(string stanza)
		{
			Hashtable	attributes;
			Hashtable	attribute;
		
			attributes = new Hashtable();
			foreach (string attributeKey in ini.Configs[stanza].Get("Attributes").Split(','))
			{
				attribute = new Hashtable();
				attribute.Add("Key", attributeKey);
				attribute.Add("Name", ini.Configs[stanza].Get(attributeKey + "_name"));
				switch (ini.Configs[stanza].Get(attributeKey + "_type"))
				{
					case "1":
							attribute.Add("Type", AttributeType.String);
						break;
					case "3":
							attribute.Add("Type", AttributeType.Mail);
						break;
					case "9":
							attribute.Add("Type", AttributeType.MultiSelect);
						break;
					case "19":
							attribute.Add("Type", AttributeType.Select);
						break;
					case "29":
							attribute.Add("Type", AttributeType.Boolean);
						break;												
					default:
						break;
				}
				attributes.Add(attributeKey, attribute);
			}
			return attributes;
		}
		
		/// \brief Return an enumerator for the phone types defined for the specified document type
		/// \param docType Document type, use the IDocument.Type property
		public ICollection PhoneTypes(DocumentType docType)
		{
				ICollection phones = null;
				switch (docType)
				{
					case DocumentType.Contact:
						phones = ((Hashtable)((Hashtable)configs["Contact"])["Phones"]).Keys;
						break;
					case DocumentType.Enterprise:
						phones = ((Hashtable)((Hashtable)configs["Enterprise"])["Phones"]).Keys;
						break;
				}
				return phones;
		}
		
		/// \brief Return an enumerator for the address types defined for the specified document type
		/// \param docType Document type, use the IDocument.Type property		
		public ICollection AddressTypes(DocumentType docType)
		{
				ICollection addresses = null;
				switch (docType)
				{
					case DocumentType.Contact:
						addresses = ((Hashtable)((Hashtable)configs["Contact"])["Addresses"]).Keys;
						break;
					case DocumentType.Enterprise:
						addresses = ((Hashtable)((Hashtable)configs["Enterprise"])["Addresses"]).Keys;
						break;
				}
				return addresses;
		}

		/// \brief Return an enumerator for the extended attributes defined for the specified document type
		/// \param docType Document type, use the IDocument.Type property		
		public ICollection AttributeTypes(DocumentType docType)
		{
				ICollection attributes = null;
				switch (docType)
				{
					case DocumentType.Contact:
						attributes = ((Hashtable)((Hashtable)configs["Contact"])["Attributes"]).Keys;
						break;
					case DocumentType.Enterprise:
						attributes = ((Hashtable)((Hashtable)configs["Enterprise"])["Attributes"]).Keys;
						break;
				}
				return attributes;
		}
		
		/// \brief Indicate if the specified type is defined in the specified category for the specified document type
		/// \param docType Document type, use the IDocument.Type property
		/// \param category Must be "Attributes", "Addresses", or "Phones"
		/// \param type The type key, and attribute name, address type, or phone number type
		internal bool SupportsType(DocumentType docType, string category, string type)
		{
			if ((category == "Attributes") ||
				(category =="Addresses") ||
				(category == "Phones"))
			{
 				switch (docType)
				{
					case DocumentType.Contact:
						return ((Hashtable)((Hashtable)configs["Contact"])[category]).Contains(type);
					case DocumentType.Enterprise:
						return ((Hashtable)((Hashtable)configs["Enterprise"])[category]).Contains(type);
				}
			}
			return false;
		}

		/// \brief Provides the server class with access to the configuration information
		/// \param stanza Stanza to retrieve directive value from
		/// \param directive The directive whose value you want returned
		internal string GetDirectiveValue(string stanza, string directive)
		{
			string directiveValue;
			
			directiveValue = null;
			if (ini.Configs[stanza].Contains(directive))
				directiveValue = ini.Configs[stanza].Get(directive);
				else
					if (ini.Configs["INI"].Contains(stanza + ":" + directive))
					{
						string[] redirect; 
						redirect = ini.Configs["INI"].Get(stanza + ":" + directive).Split(':');
						directiveValue = GetDirectiveValue(redirect[0], redirect[1]);
					}
			if (directiveValue != null)
				if (directiveValue.StartsWith("@"))
					directiveValue = GetValueFromSource(directiveValue);
			return directiveValue;
		}
		
		protected string GetValueFromSource(string source)
		{
			string referenceType;
			string	referenceURL;
			string content;
					
			referenceType = source.Substring(1, (source.IndexOf(":") - 1));
			referenceURL = source.Substring(source.IndexOf(":"));
			content = "";
			switch(referenceType)
			{
				case "file":
					content = ReadFromFile(referenceURL);
					break;
				case "ldap":
					DirectoryEntry	entry;
					string[]		urlParts;
					
					Console.WriteLine("Read attribute from LDAP");
					urlParts = referenceURL.Split(';');
					entry = new DirectoryEntry();
					entry.AuthenticationType = AuthenticationTypes.None;
					entry.Username = urlParts[0];
					if (urlParts[0].StartsWith("@"))
						entry.Password = ReadFromFile(urlParts[0].Substring(1));
						else entry.Password = urlParts[0];
					entry.Password = urlParts[1];
					entry.Path = @"LDAP://" + urlParts[2];
					content = (string)entry.Properties[urlParts[3]][0];
					break;
				case "env":
					Console.WriteLine("Read attribute from environment");
					content = Environment.GetEnvironmentVariable(referenceURL);
					break;
				default:
					break;
			}
			return content;
		}
		
		protected string ReadFromFile(string filename)
		{
			StreamReader	reader;
			string	line;
			string content;
			
			Console.WriteLine("Read attribute from file");
			content = "";
			reader = new StreamReader(filename);
      		line = reader.ReadLine();
      		while (line != null) 
      		{
        		content = content + line;
        		line = reader.ReadLine();
      		}
      		reader.Close();
      		return content;
		}
	}
}